package org.icop.frame.boost;

import java.util.Set;

import org.icop.model.MultiSet;
import org.icop.model.ValuedCorrespondence;
import org.icop.utils.GraphDistanceCache;
import org.jbpt.algo.graph.DistanceRelation;
import org.jbpt.graph.abs.IDirectedEdge;
import org.jbpt.graph.abs.IDirectedGraph;
import org.jbpt.hypergraph.abs.IVertex;

/**
 * We boost matches based on the max graph distance that holds 
 * minimally for all pairs of nodes in the set. Given a node set,
 * for each node, we determine the minimal graph distance to 
 * another node of the set. The max value of all these min values 
 * is then related to the max graph distance of all nodes in the graph.
 * 
 * We boost, if the average of (1 minus this ratio) of both node sets
 * is above a threshold. 
 * 
 * Second, we related the differences of these values for the two node
 * sets to each other. We boost, if the difference is below a threshold.
 * 
 * @author matthias.weidlich
 *
 */
public class MatchGraphDistanceBooster<G extends IDirectedGraph<E,V>, E extends IDirectedEdge<V>, V extends IVertex> implements Booster<G, E, V> {

	public static double maxDistanceBoostFactor;
	public static double boostWithMaxDistanceRatioAbove;

	public static double distanceDifferenceBoostFactor;
	public static double boostWithMaxDistanceRatioDifferenceBelow;
	
	public MatchGraphDistanceBooster() {
		this(0.5,0.5,0.5,0.2);
	}
	
	public MatchGraphDistanceBooster(
			double maxDistanceBoostFactor,
			double boostWithMaxDistanceRatioAbove,
			double distanceDifferenceBoostFactor,
			double boostWithMaxDistanceRatioDifferenceBelow) {
		MatchGraphDistanceBooster.maxDistanceBoostFactor = maxDistanceBoostFactor;
		MatchGraphDistanceBooster.boostWithMaxDistanceRatioAbove = boostWithMaxDistanceRatioAbove;
		MatchGraphDistanceBooster.distanceDifferenceBoostFactor = distanceDifferenceBoostFactor;
		MatchGraphDistanceBooster.boostWithMaxDistanceRatioDifferenceBelow = boostWithMaxDistanceRatioDifferenceBelow;
	}
	
	@Override
	public MultiSet<ValuedCorrespondence<V>> boost(G sg1, G sg2,
			MultiSet<ValuedCorrespondence<V>> mapping) {
		
		for (ValuedCorrespondence<V> sets : mapping) {
			float maxDistanceRatio1 = getMaxDistanceRatio(sg1,sets.getV1s());
			float maxDistanceRatio2 = getMaxDistanceRatio(sg2,sets.getV2s());
			
			boostBasedOnMaxDistanceRatio(sets, (maxDistanceRatio1 + maxDistanceRatio2)/2);
			boostBasedOnMaxDistanceDifference(sets,Math.abs(maxDistanceRatio1 - maxDistanceRatio2));
		}
		return mapping;
	}

	protected float getMaxDistanceRatio(G g, Set<V> nodes) {
		@SuppressWarnings("unchecked")
		DistanceRelation<E, V> distance = (DistanceRelation<E, V>) GraphDistanceCache.getInstance().getDistanceRelationForGraph((IDirectedGraph<IDirectedEdge<IVertex>, IVertex>) g);
		
		float maxInSet = 1;
		for(V n1: nodes) {
			float minForNode = Integer.MAX_VALUE;
			for(V n2: nodes) {
				if (n1.equals(n2))
					continue;
				int nodeDistance = distance.getDistanceBetweenVertices(n1, n2);
				minForNode = Math.min(minForNode, nodeDistance);
			}
			if (minForNode < Integer.MAX_VALUE)
				maxInSet = Math.max(maxInSet, minForNode);
		}
		return 1 - maxInSet / distance.getMaxDistance();
	}
	
	protected void boostBasedOnMaxDistanceRatio(ValuedCorrespondence<V> sets, float maxDistanceRatio) {
		if (maxDistanceRatio < boostWithMaxDistanceRatioAbove)
			return;
		double diff = 1 - sets.getConfidenceValue();
		sets.setConfidenceValue(sets.getConfidenceValue() + diff * maxDistanceBoostFactor * maxDistanceRatio);
	}
	
	protected void boostBasedOnMaxDistanceDifference(ValuedCorrespondence<V> sets, float ratioDifference) {
		if (ratioDifference > boostWithMaxDistanceRatioDifferenceBelow)
			return;
		double diff = 1 - sets.getConfidenceValue();
		sets.setConfidenceValue(sets.getConfidenceValue() + diff * distanceDifferenceBoostFactor * (1 - ratioDifference));
	}
}
